/*
 *    AdaGraphMiner.java
 *    Copyright (C) 2011 University of Waikato, Hamilton, New Zealand
 *    @author Albert Bifet (abifet at cs dot waikato dot ac dot nz)
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package moa.learners;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.HashSet;

import moa.MOAObject;
import moa.classifiers.ADWIN;
import moa.core.Example;
import moa.core.GraphExample;
import moa.core.Measurement;
import moa.core.StringUtils;
import moa.core.TimingUtils;
import moa.options.FlagOption;
import moa.options.FloatOption;
import moa.options.MultiChoiceOption;
import moa.options.IntOption;

import moss.Graph;
import moss.Notation;
import moss.MossGraphMiner;
import moss.TableReader;
import moss.SMILES;
import moss.outputGraph;

public class AdaGraphMiner extends AbstractLearner {

    //Auxiliar class to re-process graphs
    protected class StringGraphReader extends TableReader {

        public StringGraphReader(Reader reader, int mode, Notation ntn) {                             /* --- create a table reader */
            super(reader, mode, ntn);
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }
    }

    protected class GraphBatch {

        protected Map<Graph, Integer> batch;

        protected int size;

        public GraphBatch() {
            this.batch = new HashMap<Graph, Integer>();
            this.size = 0;
        }

        public GraphBatch(Map<Graph, Integer> batch) {
            this.batch = batch;
            this.size = this.batch.size();
        }

        public Map<Graph, Integer> getBatch() {
            return this.batch;
        }

        public void setBatch(Map<Graph, Integer> batch) {
            this.batch = batch;
        }

        public void addBatch(GraphBatch addBatch) {
            addBatch(addBatch, 1.0);
        }

        public void addBatch(GraphBatch addBatch, double alpha) {
            //System.out.println("ADDING BATCH "+this.batch.size()+" "+addBatch.getBatch().keySet().size());
            for (Graph is : addBatch.getBatch().keySet()) {
                if (this.batch.get(is) != null) {
                    this.batch.put(is, (int) ((double) this.batch.get(is) + alpha * (double) addBatch.getBatch().get(is)));
                } else {
                    this.batch.put(is, (int) (alpha * (double) addBatch.getBatch().get(is)));
                }
            }
            this.size += addBatch.size();
            //(int) (alpha * (double) addBatch.size());
        }

        public int size() {
            return this.size;
        }

        public void setSize(int size) {
            this.size = size;
        }

        public void print() {
            for (Graph is : this.batch.keySet()) {
                System.out.println(is + " has support " + this.batch.get(is));
            }
        }
    }

    protected class AdwinCollection {

        protected Map<Graph, ADWIN> batch;

        public AdwinCollection() {
            this.batch = new HashMap<Graph, ADWIN>();
        }

        public Map<Graph, ADWIN> getBatch() {
            return this.batch;
        }

        public void addAdwin(Graph g, ADWIN add) {
            this.batch.put(g, add);
        }

        public void remove(Graph g) {
            this.batch.remove(g);
        }

        public double getSupport(Graph g) {
            if (this.batch.get(g) == null) {
                return 0;
            } else {
                return this.batch.get(g).getEstimation();
            }
        }

        public boolean updateSupport(Graph g, double support) {
            if (this.batch.get(g) == null) {
                this.batch.put(g, new ADWIN());
            }
            return this.batch.get(g).setInput(support);
        }

        public void print() {
            for (Graph is : this.batch.keySet()) {
                System.out.println(is + " has support " + getSupport(is));
            }
        }
    }
    private static final long serialVersionUID = 1L;

    public IntOption windowSizeOption = new IntOption(
            "windowSize", 'w',
            "Size of the sliding window.", 100000);

    public IntOption batchSizeOption = new IntOption(
            "batchSize", 'b',
            "Size of a batch of examples.", 2000);

    public FloatOption minSupportOption = new FloatOption(
            "minSupport", 's',
            "Minimum support.", 0.1, 0, 1);

    public FloatOption alphaMinSupportOption = new FloatOption(
            "alphaMinSupport", 'a',
            "Alpha Minimum support.", 0.8, 0, 1);

    public FloatOption deltaClosedOption = new FloatOption(
            "deltaClosed", 'd',
            "Delta Closed support.", 0, 0, 1);

    public FlagOption gSpanOption = new FlagOption("gSpan", 'g',
            "Rightmost path extensions as gspan");

    public FlagOption verboseOption = new FlagOption("verbose", 'v',
            "Verbose mode");

    public MultiChoiceOption modeLearnerOption = new MultiChoiceOption(
            "modeLearner", 'm', "Mode of Learning.", new String[]{
                "INC", "WIN", "ADAWIN", "ADAEWMA", "ADAADWIN"}, new String[]{
                "Incremental",
                "Sliding Window",
                "Adaptive Sliding Window",
                "Adaptive EWMA",
                "ADWIN"}, 2);

    public static final int INC = 0;

    public static final int WIN = 1;

    public static final int ADAWIN = 2;

    public static final int ADAEWMA = 3;

    public static final int ADAADWIN = 4;

    protected int modeMining; //modeLearner

    protected int changeDetections; //modeLearner

    @SuppressWarnings("hiding")
    public static final String classifierPurposeString = "Incremental Close Graph Learner.";

    protected List<GraphBatch> window;

    protected GraphBatch currentClosedGraphs;

    protected List<GraphExample> inputBatch;

    protected int currentWindowBatch;

    protected int inputBatchGraph;

    protected ADWIN SizeWindowEstimator;

    protected long evaluateStartTime;

    protected boolean preciseCPUTiming;

    protected int numberClosedGraphs;

    protected Map<Integer, List<outputGraph>> outputGraphMining;

    protected Map<Integer, List<outputGraph>> lastBatchOutput;

    protected AdwinCollection adwinCollection;

    @Override
    public void resetLearningImpl() {
        this.preciseCPUTiming = TimingUtils.enablePreciseTiming();
        this.evaluateStartTime = TimingUtils.getNanoCPUTimeOfCurrentThread();
        this.modeMining = this.modeLearnerOption.getChosenIndex();
        if (this.modeMining == WIN || this.modeMining == ADAWIN) {
            // inits a window of batches W
            this.window = new ArrayList<GraphBatch>();
        }
        if (this.modeMining == ADAWIN || this.modeMining == ADAEWMA) {
            this.SizeWindowEstimator = new ADWIN();
        }
        if (this.modeMining == ADAADWIN) {
            this.adwinCollection = new AdwinCollection();
        }

        // inits a map of current closed frequent graphs storing graphs and supports
        this.currentClosedGraphs = new GraphBatch();
        // init batch
        this.inputBatch = new LinkedList<GraphExample>();//[this.batchSizeOption.getValue()];
        this.currentWindowBatch = 0;
        this.inputBatchGraph = 0;
        this.numberClosedGraphs = 0;
        this.changeDetections = 0;
    }

    protected GraphBatch BatchMiner(List<GraphExample> input) {
        return BatchMiner(input, this.batchSizeOption.getValue(), false);
    }
    protected MossGraphMiner graphMiner;

    protected GraphBatch BatchMiner(List<GraphExample> input, int size, boolean addingBatches) {
        GraphBatch batch = new GraphBatch();
        //System.err.println("Batch size "+input.size()+" "+size);
        //Init the miner
        this.graphMiner = new MossGraphMiner();
        String args = "";
        if (gSpanOption.isSet()) {
            args = "-g ";
        }
        if (verboseOption.isSet()) {
            args = "-v ";
        }
        args += "example1.smiles moss.sub";
        try {
            //Prepare the mining
            graphMiner.init(args.split(" "));
            graphMiner.resetLearningImpl();
            //Set input graphs for the miner
            for (GraphExample ge : input) {
                if (ge != null) {
                    graphMiner.addGraph(ge.getNamedGraph());
                    //if (addingBatches)
                    //System.err.println("Batch "+this.currentWindowBatch+" "+ge.getNamedGraph()+" weight: "+ge.weight());
                } else {
                    System.err.println("ERR ge == null");
                }

            }
            //Do the mining
            graphMiner.setSupport(minSupportOption.getValue() * alphaMinSupportOption.getValue(), size);
            graphMiner.setDeltaClosed(deltaClosedOption.getValue());
            graphMiner.runAlgorithm();
            //graphMiner.term();
            //graphMiner.stats();
            //this.numberClosedGraphs = graphMiner.computeNumberClosedGraphs(minSupportOption.getValue(), size);

            if (!addingBatches) {
                this.lastBatchOutput = graphMiner.getOutput();
            }
            if (addingBatches || this.currentWindowBatch == 0) {
                this.outputGraphMining = graphMiner.getOutput();
            } else {
                if (this.modeMining != ADAADWIN) {
                    //Give more importance to new graphs
                    EmphasizeNewGraphs(graphMiner.getOutput());
                }
            }
            /*if (this.modeMining == ADAADWIN && addingBatches) {
            //Update Adwins using support from previous AddMiner
            for ( Integer support: this.outputGraphMining.keySet()){ //from smaller to larger
            for ( outputGraph is : this.outputGraphMining.get(support)){
            adwinCollection.updateSupport(is.getGraph(), is.getSupport());
            }
            }
            //Update supports in graphMiner
            }*/
            //graphMiner.computeRelativeSupport();
            //batch.setBatch(graphMiner.getOutputWithRelativeSupport());
            batch.setSize(size);
        } catch (IOException e) {     /* report i/o error message */
            System.err.println("\n" + e.getMessage());
        }
        return batch;
    }

    protected void computeRelativeSupportMiner(GraphBatch gb) {
        this.graphMiner.computeRelativeSupport();
        gb.setBatch(this.graphMiner.getOutputWithRelativeSupport());
    }

    protected List<GraphExample> preprocessGraphs(GraphBatch input) {
        List<GraphExample> output = new LinkedList<GraphExample>();//GraphExample[input.size()];
        int counter = 0;
        //System.out.println("Preprocessing "+input.size()+" "+input.getBatch().keySet().size());
        for (Graph is : input.getBatch().keySet()) {
            if (input.getBatch().get(is) != 0) {
                //System.out.println("Preprocessing "+is.toString());
                StringGraphReader reader = new StringGraphReader(new StringReader(""), 0, new SMILES()); //GRAPHS is 0
                reader.setDesc(is.toString());
                try {
                    Graph graph = reader.getGraph(); //Obtain a new graph equivalent to is
                    GraphExample addGraph = new GraphExample(graph, "a" + counter, 0, 0);
                    // Negative weight will delete the graphs
                    addGraph.setWeight(input.getBatch().get(is));
                    output.add(addGraph);
                    counter++;
                    //System.err.println(counter+" "+graph+" delete is "+delete+ " support is "+input[counter-1].weight());
                } catch (IOException e) {     /* report i/o error message */
                    System.err.println("\n" + e.getMessage());
                }
            }
        }
        //System.out.println("Preprocessing output "+output.size());
        return output;
    }

    protected boolean isNewGraph(Graph g, List<GraphExample> output) {

        //g.decode();
        g.prepareEmbed();
        // Find the support of its closure ( supergrah with smallest support)
        for (GraphExample ge : output) {
            Graph isg = ge.getNamedGraph();
            isg.decode();
            isg.prepare();         /* and then embed the subgraph */
            //System.out.println(is.getGraph()+(is.getGraph().contains(g) ? " YES" : " NO" )+ " contains " + g);
            if (isg.contains(g)) {
                return false;
            }
        }
        return true;
    }

    protected void EmphasizeNewGraphs(Map<Integer, List<outputGraph>> add) {
        // for each graph in add
        // if it was not frequent Emphasize its support

        GraphBatch graphBatch = new GraphBatch();
        //for ( Integer support: add.keySet()){ //from smaller to larger
        //	for ( outputGraph is : add.get(support)){
        for (Integer support : this.outputGraphMining.keySet()) { //from smaller to larger
            for (outputGraph is : this.outputGraphMining.get(support)) {
                graphBatch.getBatch().put(is.getGraph(), is.getSupport());
            }
        }
        List<GraphExample> currentGraphs = preprocessGraphs(graphBatch);
        /*System.out.println("LIST CURRENTGRAPHS");
        for (GraphExample ge : currentGraphs) {
        System.out.println(ge.getNamedGraph());
        }*/

        graphBatch = new GraphBatch();
        for (Integer support : add.keySet()) { //from smaller to larger
            for (outputGraph is : add.get(support)) {
                graphBatch.getBatch().put(is.getGraph(), is.getSupport());
            }
        }
        //1- Prepare GraphNamed
        List<GraphExample> newGraphs = preprocessGraphs(graphBatch);

        for (Integer support : add.keySet()) { //from smaller to larger
            for (outputGraph isg : add.get(support)) {
                Graph is = isg.getGraph();
                for (GraphExample ge : newGraphs) {
                    //System.out.println("SIM Emphasize "+is+" - "+ge.getNamedGraph());
                    if (ge.getNamedGraph().toString().equals(is.toString())) {
                        //System.out.println("TRUE SIM Emphasize "+is.toString());
                        if (isNewGraph(ge.getNamedGraph(), currentGraphs) == true) {
                            int size = this.currentClosedGraphs.size() + this.batchSizeOption.getValue();
                            System.out.print("Emphasize " + isg.getSupport());
                            //addBatch.getBatch().put(is, addBatch.getBatch().get(is) * size / addBatch.size() );
                            isg.setSupport(isg.getSupport() * size / this.batchSizeOption.getValue());
                            isg.setRelativeSupport(isg.getSupport());
                            System.out.println(" Current Support (" + is + ") is " + isg.getSupport()
                                    + "," + size + "," + this.batchSizeOption.getValue());
                        }
                    }
                }
            }
        }
    }

    protected void AddMiner(GraphBatch addBatch, double alphaEWMA) {
        //Do mining with the current closed graphs, and the graphs to add using EWMA support
        int size = this.currentClosedGraphs.size() + addBatch.size();
        //Give more importance to new graphs
        //EmphasizeNewGraphs(addBatch);
        GraphBatch input = new GraphBatch();
        if (this.currentClosedGraphs.size() != 0) {
            double sizeFactor = size / this.currentClosedGraphs.size();
            //double sizeFactor = size/(this.currentClosedGraphs.size()*(1.0-alphaEWMA) + alphaEWMA*addBatch.size());
            input.addBatch(this.currentClosedGraphs, (1.0 - alphaEWMA) * sizeFactor); //*sizeFactor
            sizeFactor = size / addBatch.size();
            input.addBatch(addBatch, alphaEWMA * sizeFactor);
        } else {
            input.addBatch(this.currentClosedGraphs);
            input.addBatch(addBatch);
        }
        //System.out.println("***Adding Batch alpha "+this.currentWindowBatch +" Size: "+size);
        this.currentClosedGraphs = BatchMiner(preprocessGraphs(input), size, true);
        this.computeRelativeSupportMiner(this.currentClosedGraphs);

    }

    protected void AddMiner(GraphBatch addBatch) {
        AddMiner(addBatch, null);
    }

    protected void AddMiner(GraphBatch addBatch, GraphBatch deleteBatch) {
        // If this is the first batch, don't do any more mining
        int size = this.currentClosedGraphs.size() + addBatch.size();
        if (this.currentWindowBatch == 0) {
            this.currentClosedGraphs = addBatch;
            //System.out.println("***Adding First Batch " + this.currentWindowBatch + " Size: " + addBatch.getBatch().size());
            if (this.modeMining == ADAADWIN) {
                //Create Collection of Adwins
                for (Integer support : this.outputGraphMining.keySet()) { //from smaller to larger
                    for (outputGraph is : this.outputGraphMining.get(support)) {
                        adwinCollection.updateSupport(is.getGraph(), ((double) is.getSupport()) / (double) addBatch.size());
                    }
                }
            }
            this.numberClosedGraphs = graphMiner.computeNumberClosedGraphs(minSupportOption.getValue(), size);
            return;
        }
        //Do mining with the current closed graphs, the graphs to add and the graphs to delete

        GraphBatch input = new GraphBatch();

        input.addBatch(this.currentClosedGraphs);
        input.addBatch(addBatch);
        if (deleteBatch != null) {
            input.addBatch(deleteBatch, -1);
            size -= deleteBatch.size();
        }
        //System.out.println("***Adding Batch " + this.currentWindowBatch + " Size: " + size + (deleteBatch == null ? "" : " Delete"));
        this.currentClosedGraphs = BatchMiner(preprocessGraphs(input), size, true);
        if (this.modeMining == ADAADWIN) {
            //1-Update supports Adwins in this.outputGraphMining;
            GraphBatch currentGraphs = new GraphBatch();
            for (Integer support : this.outputGraphMining.keySet()) { //from smaller to larger
                for (outputGraph is : this.outputGraphMining.get(support)) {
                    if (adwinCollection.updateSupport(is.getGraph(), ((double) is.getSupport()) / (double) addBatch.size())) {
                        this.changeDetections++;
                    }
                    currentGraphs.getBatch().put(is.getGraph(), is.getSupport());
                }
            }
            //Create currentGraphs and newGraphs as maps of <Graphs, support>
            GraphBatch newGraphs = new GraphBatch();
            for (Integer support : this.lastBatchOutput.keySet()) { //from smaller to larger
                for (outputGraph is : this.lastBatchOutput.get(support)) {
                    newGraphs.getBatch().put(is.getGraph(), is.getSupport());
                }
            }

            //2-Update support Adwins with 0 support in the last batch;
            Set<Graph> diff = new HashSet<Graph>(adwinCollection.getBatch().keySet());
            diff.removeAll(currentGraphs.getBatch().keySet());
            for (Graph is : diff) {
                //Update support
                if (adwinCollection.updateSupport(is, 0)) {
                    this.changeDetections++;
                }
            }

            //this.adwinCollection.print();
            //3-Update supports this.outputGraphMining;
            Set<outputGraph> removeGraphs = new HashSet<outputGraph>();
            Set<outputGraph> addGraphs = new HashSet<outputGraph>();
            for (Integer support : this.outputGraphMining.keySet()) { //from smaller to larger
                for (outputGraph is : this.outputGraphMining.get(support)) {
                    int newSupport = (int) (size * this.adwinCollection.getSupport(is.getGraph()));
                    if (newSupport != support) {
                        //Delete and add
                        removeGraphs.add(is);
                        if (newSupport >= size * minSupportOption.getValue() * alphaMinSupportOption.getValue()) {
                            addGraphs.add(is);
                            is.setSupport(newSupport);
                            is.setRelativeSupport(newSupport);
                            //System.out.println(is.getGraph()+" Changing Support from "+support+" to "+newSupport);
                        }
                    }
                }
                for (outputGraph is : removeGraphs) {
                    this.outputGraphMining.get(support).remove(is);
                }
                removeGraphs = new HashSet<outputGraph>();
            }
            for (outputGraph is : addGraphs) {
                if (this.outputGraphMining.get(is.getSupport()) == null) {
                    this.outputGraphMining.put(is.getSupport(), new ArrayList<outputGraph>());
                }
                List<outputGraph> list = this.outputGraphMining.get(is.getSupport());
                list.add(is);

            }

            //4-Update support Adwins on new graphs that appear only on the addBatch but not before

            diff = new HashSet<Graph>(newGraphs.getBatch().keySet());
            diff.removeAll(currentGraphs.getBatch().keySet());
            for (Graph is : diff) {
                //System.out.println("NEW SUPPORT "+//newGraphs.getBatch().get(is)/addBatch.size()+","+
                //	size * newGraphs.getBatch().get(is)/addBatch.size()+","+is);
                //Update support
                if (adwinCollection.updateSupport(is, ((double) newGraphs.getBatch().get(is)) / (double) addBatch.size())) {
                    this.changeDetections++;
                }
                //Also add to outputGraphMining
                int newSupport = (int) (size * newGraphs.getBatch().get(is) / addBatch.size());
                if (this.outputGraphMining.get(newSupport) == null) {
                    this.outputGraphMining.put(newSupport, new ArrayList<outputGraph>());
                }
                List<outputGraph> list = this.outputGraphMining.get(newSupport);
                list.add(new outputGraph(is, is.getNumberNodes() + is.getNumberEdges(), newSupport));
            }

            //Print list of graphs
            for (Integer support : this.outputGraphMining.keySet()) { //from smaller to larger
                for (outputGraph is : this.outputGraphMining.get(support)) {
                    //System.out.println("== "+support+" "+is.getGraph());
                }
            }
        }
        this.numberClosedGraphs = graphMiner.computeNumberClosedGraphs(minSupportOption.getValue(), size);
        this.computeRelativeSupportMiner(this.currentClosedGraphs);
    }

    @Override
    public void trainOnInstanceImpl(Example inst) {
        // every new graph is stored in a batch
        this.inputBatch.add((GraphExample) inst);
        this.inputBatchGraph++;

        // when the current batch is full
        if (this.inputBatchGraph == this.batchSizeOption.getValue()) {

            // compute the closed frequent graphs using a batch method and store them in the window
            //System.out.println("***Computing Batch " + this.currentWindowBatch);
            //System.out.println();
            GraphBatch newBatch = BatchMiner(this.inputBatch);
            this.computeRelativeSupportMiner(newBatch);

            switch (this.modeMining) {
                case INC:
                    AddMiner(newBatch);
                    break;
                case WIN:
                    this.window.add(newBatch);
                    // if the window is full remove data from oldest batch and add these new frequent graphs,
                    if (this.window.size() * this.batchSizeOption.getValue() > this.windowSizeOption.getValue()) {
                        AddMiner(newBatch, this.window.get(0));
                        this.window.remove(0);
                    } else {
                        // otherwise, only add these new frequent graphs
                        AddMiner(newBatch);
                    }
                    break;
                case ADAWIN:
                    // remove data to keep the same size as in ADWIN and add the new frequent graphs,
                    GraphBatch removeBatch = new GraphBatch();
                    while (this.window.size() > this.SizeWindowEstimator.getWidth()) {
                        removeBatch.addBatch(this.window.get(0));
                        this.window.remove(0);
                    }
                    this.changeDetections += this.SizeWindowEstimator.setInput(this.currentClosedGraphs.getBatch().size()) ? 1.0 : 0.0;
                    AddMiner(newBatch, removeBatch);
                    this.window.add(newBatch);
                    break;
                case ADAEWMA:
                    // Compute using EWMA
                    AddMiner(newBatch, 1.0 / (1.0 + (double) this.SizeWindowEstimator.getWidth()));
                    this.changeDetections += this.SizeWindowEstimator.setInput(this.currentClosedGraphs.getBatch().size()) ? 1.0 : 0.0;
                    break;
                case ADAADWIN:
                    //Use Adwins inside AddMiner
                    AddMiner(newBatch);
                    break;
            }
            // start a new batch
            this.currentWindowBatch++;
            this.inputBatch = null;
            this.inputBatch = new LinkedList<GraphExample>();
            this.inputBatchGraph = 0;
        }
    }

    @Override
    protected Measurement[] getModelMeasurementsImpl() {
        long evaluateTime = TimingUtils.getNanoCPUTimeOfCurrentThread();
        double time = TimingUtils.nanoTimeToSeconds(evaluateTime - this.evaluateStartTime);
        List<Measurement> measurementList = new LinkedList<Measurement>();
        measurementList.add(new Measurement("model total memory (Megabytes)",
                Runtime.getRuntime().totalMemory() / (1024 * 1024)));
        measurementList.add(new Measurement("model time (" + (preciseCPUTiming ? "cpu " : "") + "seconds)", time));
        measurementList.add(new Measurement("number of closed graphs", this.numberClosedGraphs));
        measurementList.add(new Measurement("number of changes detected", this.changeDetections));
        measurementList.add(new Measurement("size of window", this.SizeWindowEstimator != null ? this.SizeWindowEstimator.getWidth() : 0.0));
        this.changeDetections = 0;
        return measurementList.toArray(new Measurement[measurementList.size()]);
    }

    @Override
    public void getModelDescription(StringBuilder out, int indent) {
        // Output closed frequent graphs
        StringUtils.appendIndented(out, indent, "Number of closed frequent graphs: ");
        out.append(this.currentClosedGraphs.getBatch().size());
        StringUtils.appendNewline(out);
        for (Graph is : this.currentClosedGraphs.getBatch().keySet()) {
            StringUtils.appendIndented(out, indent, "Support of <");
            out.append(is);
            out.append(" >: ");
            out.append(this.currentClosedGraphs.getBatch().get(is));
            StringUtils.appendNewline(out);
        }

    }

    @Override
    public MOAObject getModel() {
        return null;
    }

    public boolean isRandomizable() {
        return false;
    }
}
